<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.4/css/all.min.css" integrity="sha256-mUZM63G8m73Mcidfrv5E+Y61y7a12O5mW4ezU3bxqW4=" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.1.1/animate.min.css" integrity="sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE=" crossorigin="anonymous">

<script class="next-config" data-name="main" type="application/json">{"hostname":"pengyz.com","root":"/","images":"/images","scheme":"Pisces","version":"8.7.1","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":false,"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果：${query}","hits_time":"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false}}</script><script src="/js/config.js"></script>
<meta name="description" content="对调试器的最初印象每个刚入行的程序员都有过对调试器一知半解的时候，那时我们使用 printf，console.log, System.out.println 等进行错误定位，统称打印输出调试法，这个方法至今在服务器后端等无法停机更无法挂调试器的地方大行其道，我们叫它log日志。当我们工作了一段时间之后，逐渐发现了编程世界的复杂性，有些问题无法通过简单的打印来定位：  程序内某个变量的值被改掉了，我">
<meta property="og:type" content="article">
<meta property="og:title" content="quickjs调试机制实现解析">
<meta property="og:url" content="http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/index.html">
<meta property="og:site_name" content="Alex的技术博客">
<meta property="og:description" content="对调试器的最初印象每个刚入行的程序员都有过对调试器一知半解的时候，那时我们使用 printf，console.log, System.out.println 等进行错误定位，统称打印输出调试法，这个方法至今在服务器后端等无法停机更无法挂调试器的地方大行其道，我们叫它log日志。当我们工作了一段时间之后，逐渐发现了编程世界的复杂性，有些问题无法通过简单的打印来定位：  程序内某个变量的值被改掉了，我">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-22-15-06-58.png">
<meta property="og:image" content="http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-18-10-57-34.png">
<meta property="og:image" content="http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-23-10-26-16.png">
<meta property="og:image" content="http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-18-19-09-56.png">
<meta property="article:published_time" content="2021-09-17T12:43:21.000Z">
<meta property="article:modified_time" content="2021-09-23T02:29:53.375Z">
<meta property="article:author" content="Alex Peng">
<meta property="article:tag" content="quickjs">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-22-15-06-58.png">


<link rel="canonical" href="http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/">



<script class="next-config" data-name="page" type="application/json">{"sidebar":"","isHome":false,"isPost":true,"lang":"zh-CN","comments":true,"permalink":"http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/","path":"2021/09/17/quickjs调试机制实现解析/","title":"quickjs调试机制实现解析"}</script>

<script class="next-config" data-name="calendar" type="application/json">""</script>
<title>quickjs调试机制实现解析 | Alex的技术博客</title>
  




  <noscript>
    <link rel="stylesheet" href="/css/noscript.css">
  </noscript>
<link rel="alternate" href="/atom.xml" title="Alex的技术博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">Alex的技术博客</h1>
      <i class="logo-line"></i>
    </a>
      <p class="site-subtitle" itemprop="description">不忘初心，方得始终</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu">
        <li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li>
        <li class="menu-item menu-item-about"><a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a></li>
        <li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li>
        <li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li>
        <li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup"><div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off" maxlength="80"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close" role="button">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div class="search-result-container no-result">
  <div class="search-result-icon">
    <i class="fa fa-spinner fa-pulse fa-5x"></i>
  </div>
</div>

    </div>
  </div>

</div>
        
  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>

  <aside class="sidebar">

    <div class="sidebar-inner sidebar-nav-active sidebar-toc-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
            <div class="post-toc animated"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%AF%B9%E8%B0%83%E8%AF%95%E5%99%A8%E7%9A%84%E6%9C%80%E5%88%9D%E5%8D%B0%E8%B1%A1"><span class="nav-number">1.</span> <span class="nav-text">对调试器的最初印象</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E8%B0%83%E8%AF%95%E5%99%A8%E6%98%AF%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0%E7%9A%84"><span class="nav-number">2.</span> <span class="nav-text">调试器是怎么实现的</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%B0%83%E8%AF%95%E5%99%A8%E7%9A%84%E6%9E%B6%E6%9E%84"><span class="nav-number">2.1.</span> <span class="nav-text">调试器的架构</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#VSCode%E5%92%8CDAP%E5%8D%8F%E8%AE%AE"><span class="nav-number">2.2.</span> <span class="nav-text">VSCode和DAP协议</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%B0%83%E8%AF%95%E5%99%A8%E5%90%8E%E7%AB%AF%E5%AE%9E%E7%8E%B0"><span class="nav-number">2.3.</span> <span class="nav-text">调试器后端实现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A6%82%E4%BD%95%E6%9A%82%E5%81%9C"><span class="nav-number">2.3.1.</span> <span class="nav-text">如何暂停</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A6%82%E4%BD%95%E5%9C%A8%E6%BA%90%E7%A0%81%E5%B1%82%E9%9D%A2%E5%91%BD%E4%B8%AD%E6%96%AD%E7%82%B9%E5%92%8C%E5%8D%95%E6%AD%A5%E6%89%A7%E8%A1%8C"><span class="nav-number">2.3.2.</span> <span class="nav-text">如何在源码层面命中断点和单步执行</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A6%82%E4%BD%95%E6%9F%A5%E7%9C%8B%E5%92%8C%E4%BF%AE%E6%94%B9%E5%8F%98%E9%87%8F%E7%9A%84%E5%80%BC"><span class="nav-number">2.3.3.</span> <span class="nav-text">如何查看和修改变量的值</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAjs-debugger"><span class="nav-number">3.</span> <span class="nav-text">怎么实现一个js debugger</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#quickjs%E8%B0%83%E8%AF%95%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0"><span class="nav-number">3.1.</span> <span class="nav-text">quickjs调试功能实现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#JS%E5%87%BD%E6%95%B0%E5%8A%9F%E8%83%BD%E6%89%A7%E8%A1%8C%E5%85%A5%E5%8F%A3"><span class="nav-number">3.1.1.</span> <span class="nav-text">JS函数功能执行入口</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%B0%83%E8%AF%95%E5%8A%9F%E8%83%BD%E5%85%A5%E5%8F%A3"><span class="nav-number">3.1.2.</span> <span class="nav-text">调试功能入口</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%B0%83%E8%AF%95%E5%91%BD%E4%BB%A4%E5%A4%84%E7%90%86"><span class="nav-number">3.1.3.</span> <span class="nav-text">调试命令处理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#quickjs%E8%B0%83%E8%AF%95%E5%8A%9F%E8%83%BD%E5%B0%8F%E7%BB%93"><span class="nav-number">3.1.4.</span> <span class="nav-text">quickjs调试功能小结</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%B7%B1%E5%85%A5%E6%8E%A2%E7%A9%B6%EF%BC%9Aquickjs%E6%96%AD%E7%82%B9%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3"><span class="nav-number">3.2.</span> <span class="nav-text">深入探究：quickjs断点实现原理详解</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#js%E5%87%BD%E6%95%B0%E5%AE%9A%E4%B9%89%E7%BB%93%E6%9E%84%E4%BD%93"><span class="nav-number">3.2.1.</span> <span class="nav-text">js函数定义结构体</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#eval%E6%97%B6%E8%A7%A3%E6%9E%90js%E6%BA%90%E7%A0%81"><span class="nav-number">3.2.2.</span> <span class="nav-text">eval时解析js源码</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%94%9F%E6%88%90%E5%AD%97%E8%8A%82%E7%A0%81%E6%97%B6%E4%BF%9D%E5%AD%98%E8%A1%8C%E5%8F%B7%E4%BF%A1%E6%81%AF"><span class="nav-number">3.2.3.</span> <span class="nav-text">生成字节码时保存行号信息</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%9B%E5%BB%BA%E5%87%BD%E6%95%B0%E5%B9%B6%E4%BF%9D%E5%AD%98pc2line%E4%BF%A1%E6%81%AF"><span class="nav-number">3.2.4.</span> <span class="nav-text">创建函数并保存pc2line信息</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%B0%83%E8%AF%95%E4%BF%A1%E6%81%AF%E7%94%9F%E6%88%90%E6%B5%81%E7%A8%8B"><span class="nav-number">3.2.5.</span> <span class="nav-text">调试信息生成流程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8pc2line%E4%BF%A1%E6%81%AF%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%91%BD%E4%B8%AD%E6%96%AD%E7%82%B9"><span class="nav-number">3.2.6.</span> <span class="nav-text">使用pc2line信息检查是否命中断点</span></a></li></ol></li></ol></li></ol></div>
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-overview">
            <div class="site-author site-overview-item animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Alex Peng"
      src="/images/avata.png">
  <p class="site-author-name" itemprop="name">Alex Peng</p>
  <div class="site-description" itemprop="description">我的个人技术随笔</div>
</div>
<div class="site-state-wrap site-overview-item animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
        <a href="/archives/">
          <span class="site-state-item-count">15</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
          <a href="/categories/">
        <span class="site-state-item-count">17</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
          <a href="/tags/">
        <span class="site-state-item-count">15</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author site-overview-item animated">
      <span class="links-of-author-item">
        <a href="https://github.com/pengyz" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;pengyz" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:530948159@qq.com" title="E-Mail → mailto:530948159@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>



          </div>
        </div>
      </div>
    </div>
  </aside>
  <div class="sidebar-dimmer"></div>


    </header>

    
  <div class="back-to-top" role="button" aria-label="返回顶部">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


    <div class="main-inner post posts-expand">


  


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://pengyz.com/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avata.png">
      <meta itemprop="name" content="Alex Peng">
      <meta itemprop="description" content="我的个人技术随笔">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Alex的技术博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          quickjs调试机制实现解析
        </h1>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2021-09-17 20:43:21" itemprop="dateCreated datePublished" datetime="2021-09-17T20:43:21+08:00">2021-09-17</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2021-09-23 10:29:53" itemprop="dateModified" datetime="2021-09-23T10:29:53+08:00">2021-09-23</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E6%8A%80%E6%9C%AF/" itemprop="url" rel="index"><span itemprop="name">技术</span></a>
        </span>
          ，
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E6%8A%80%E6%9C%AF/quickjs/" itemprop="url" rel="index"><span itemprop="name">quickjs</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
        <h1 id="对调试器的最初印象"><a href="#对调试器的最初印象" class="headerlink" title="对调试器的最初印象"></a>对调试器的最初印象</h1><p>每个刚入行的程序员都有过对调试器一知半解的时候，那时我们使用 <strong>printf</strong>，<strong>console.log</strong>, <strong>System.out.println</strong> 等进行错误定位，统称<strong>打印输出调试法</strong>，这个方法至今在服务器后端等无法停机更无法挂调试器的地方大行其道，我们叫它<strong>log日志</strong>。<br>当我们工作了一段时间之后，逐渐发现了编程世界的复杂性，有些问题无法通过简单的打印来定位：</p>
<ol>
<li>程序内某个变量的值被改掉了，我想知道是哪里改的</li>
<li>两个相同的组件，一个表现正常一个不正常，why?</li>
<li>程序存在死锁或其他多线程静态条件，怎么定位和修复？</li>
<li>我的程序内存泄露了，怎么办</li>
</ol>
<p>上面列举的一些情况有些可以借助debugger，有些如内存泄露，还需要借助其他的分析工具，但不可否认的是，debugger是程序员修bug奋斗之路上最坚实的后盾。代码出了问题，你能依靠的只有你自己的逻辑和你手里的调试工具。哦，当然，你也可以选择摇人，这个不在本文讨论范围之内。</p>
<p>我们都会用到debugger，它一般提供如下功能：</p>
<ol>
<li>控制程序运行，如断点，单步执行等</li>
<li>查看，修改变量值</li>
<li>修改程序执行流程</li>
</ol>
<p>调试功能我们常用，但就是因为它太常用了，是语言附属的基础设施，所以我们好像很少会去思考，它是如何实现的？</p>
<span id="more"></span>

<h1 id="调试器是怎么实现的"><a href="#调试器是怎么实现的" class="headerlink" title="调试器是怎么实现的"></a>调试器是怎么实现的</h1><p>在聊JS调试器之前，我们先聊一聊关于调试器更通用的一些东西，理解软件调试技术在实现层面的一些思路和细节。</p>
<h2 id="调试器的架构"><a href="#调试器的架构" class="headerlink" title="调试器的架构"></a>调试器的架构</h2><p>传统的调试器实现，一般分为调试器前端（Fortended）和调试器后端（Backend）。前端用于展示，后端实现调试功能。这种架构很经典，后端专注于调试功能实现，前端专注于展示界面，前后端之间使用IPC(包括但不限于命名管道，socket，共享内存，内存文件等)进行通信。</p>
<h2 id="VSCode和DAP协议"><a href="#VSCode和DAP协议" class="headerlink" title="VSCode和DAP协议"></a>VSCode和DAP协议</h2><p>在VSCode出现之前，一个单纯的editor是很难和调试器进行整合的，面临如下困难：</p>
<ol>
<li>需要支持多个调试器，传统的editor如vim，emacs等难以提供统一的调试界面。</li>
<li>不同的调试后端之间并没有提供统一的接口，对接不通语言的调试器或者统一语言的不通调试器实现工作量巨大</li>
<li>不同的调试器功能并不完全一致，没办法统一去实现，前端的实现和特定后端绑定</li>
</ol>
<p>软件行业有一条“<strong>金科玉律</strong>“：</p>
<blockquote>
<p>计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决</p>
<div align="right">————佚名</div>
</blockquote>
<p>为了克服上述困难，为不同语言的调试器提供统一支持，微软给出了这个“<strong><strong>中间层</strong></strong>“，推出了<a target="_blank" rel="noopener" href="https://microsoft.github.io/debug-adapter-protocol/specification">DAP协议</a>。<br>DAP是基于JSON实现的通信协议，对调试功能进行了消息通信层面的标准化，VSCode作为调试器的通用前端，开发者可以专注于实现后端。后端要做的就是接收JSON request，执行功能，发送JSON reply。</p>
<p><strong>调试功能传统实现和DAP协议之后的标准实现对比：</strong><br><img src="/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-22-15-06-58.png" alt="未使用DAP协议时调试功能的实现"><br><img src="/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-18-10-57-34.png" alt="DAP协议下调试器的实现"></p>
<p>软件行业的杀手锏就是<strong><strong>分层</strong></strong>和<strong><strong>标准化</strong></strong>，两者往往相辅相成：一旦某个部分被标准化，它往往会成为一个独立的协议和接口层。当某个东西标准化并被接受成为事实标准，一切就变得简单起来了。标准化是通用的基础，这种东西业内俗称“<strong><strong>轮子</strong></strong>”，可以极大地降低功能开发的复杂度。</p>
<p>DAP协议和VSCode调试界面，不在本文的讨论范围之内。对调试器前端实现感兴趣的同学可以参考<a target="_blank" rel="noopener" href="https://github.com/microsoft/vscode-mock-debug">vscode-mock-debug</a>，它给出了一个调试器前端的mock实现。<br>本文中我们主要聊调试器后端的实现。</p>
<h2 id="调试器后端实现"><a href="#调试器后端实现" class="headerlink" title="调试器后端实现"></a>调试器后端实现</h2><p>接下来我们重点聊一聊调试器后端要实现的主要功能和实现思路。</p>
<h3 id="如何暂停"><a href="#如何暂停" class="headerlink" title="如何暂停"></a>如何暂停</h3><p>debugger的一个核心功能便是要让程序能够在特定的情况下中断执行（命中断点），然后让用户控制程序一步一步往下跑，以观察执行过程中程序具体行为，每一步操作是否符合预期。所以这里<strong>暂停执行</strong>是关键。<br>所以，谁才有能力暂停一个程序的执行呢？如果你使用的是C/C++/Rust/go这种可以编译到汇编的语言，进程，线程等机制是由操作系统实现的，进程和线程的调度也是由系统实现，所以操作系统理应有能力<strong>暂停</strong>一个程序的执行。所以聪明的你可能想到了，对于这类程序，调试功能是由<strong>操作系统</strong>提供的，具体来说是操作系统的<strong>调试子系统</strong>。这类语言的调试器直接依赖于操作系统提供的调试API来实现（linux上一般是gdb/lldb）。<br>我们按照这个思路递推，如果是js呢？谁有能力暂停一个js程序的执行？理所应当是js引擎。所以对于js语言，调试功能应该在<strong>js引擎</strong>层予以支持和实现。</p>
<h3 id="如何在源码层面命中断点和单步执行"><a href="#如何在源码层面命中断点和单步执行" class="headerlink" title="如何在源码层面命中断点和单步执行"></a>如何在源码层面命中断点和单步执行</h3><p>我们把这两个放在一起说，因为它们具有一定的相似性，都是要完成从源码到字节码的转换。</p>
<p>语言的编译是一个翻译过程，一般用于将一种高级的，表达能力强的语言，翻译成一种低级的，表达能力弱的语言或者是生成对应的字节码。</p>
<blockquote>
<p>对于C/C++语言，编译后生成的是二进制程序，它直接对应于机器汇编，由cpu直接执行。<br>对于js语言，编译后生成js字节码，由js引擎解释执行。</p>
</blockquote>
<p>一般来说，一句C或者js代码经过编译后会生成多条汇编指令或者字节码指令。这是由<strong>信息密度</strong>决定的，高级语言的信息密度比汇编/字节码要高（这也是为什么我们不直接写汇编/字节码，而是搞了个编译器去生成它），所以翻译后会带来操作（汇编指令/字节码指令）数量的膨胀。当我们实现调试器的时候，我们要面临的首要问题就是：</p>
<blockquote>
<p>如何将字节码/汇编代码与JS/C++源码进行对应？</p>
</blockquote>
<p>解决方案是什么？其实并不复杂，我们需要一个映射关系，用来告诉debugger，字节码执行到这里，应该对应到源码的哪个文件的哪一行，用户单步执行一次，需要执行几个字节码指令。断点的设置也是一样，我们需要知道用户在某个文件的某一行设置的断点到底应该对应到汇编/字节码的哪个指令上。<br>（有没有同学想到js里的source map?思路是一样的）<br>命中断点则是在每次执行时，检查当前的程序计数器PC是否执行到了我们设置断点的地方，即可得知是否命中断点，如果命中断点则中断程序的执行（对C++,暂停其所在线程的执行，对JS，不再继续解释下一条指令）。<br>这个映射关系在C++里被称作调试符号，windows上vs会单独生成这个文件后缀名是.pdb（Programing Database），linux上gcc选择将调试符号直接嵌入到二进制文件中，可以通过strip命令去除。<br>在js里，好像并不单独存在这样一个文件，因为js编译生成的字节码一般情况下并不会被保存在硬盘上，quickjs中，调试信息被保存在了编译生成的字节码中。</p>
<h3 id="如何查看和修改变量的值"><a href="#如何查看和修改变量的值" class="headerlink" title="如何查看和修改变量的值"></a>如何查看和修改变量的值</h3><p>debugger接管了程序的执行过程，在程序暂停时，是可以获取和修改变量值的。<br>对于JS来说，变量都是通过C结构体模拟的，其中包含有变量的类型信息，变量查看和修改比较好实现。我们可以通过js context获取到所有的全局变量和局部变量的值，对它们的修改自然也不在话下。<br>对于C++这种编译型语言，由于编译时丢失了类型信息，查看和修改变量值的过程比较麻烦，需要对内存进行转换，我们不再展开。</p>
<h1 id="怎么实现一个js-debugger"><a href="#怎么实现一个js-debugger" class="headerlink" title="怎么实现一个js debugger"></a>怎么实现一个js debugger</h1><p>聊了这么久，我们终于开到了喜闻乐见的<strong>show me the code</strong>环节。在这里我们解析quickjs中针对调试功能的具体实现，我们主要关注整体流程，忽略无关且繁杂枝节，以期对实现原理有个大概的印象。</p>
<h2 id="quickjs调试功能实现"><a href="#quickjs调试功能实现" class="headerlink" title="quickjs调试功能实现"></a>quickjs调试功能实现</h2><p>现在我们会结合<a target="_blank" rel="noopener" href="https://github.com/koush/quickjs">quickjs源码</a>来大致浏览一下quickjs中调试功能的实现方式。quickjs本身是不支持调试的，现在使用的调试功能是第三方的开源实现，整体实现思路相当的简单粗暴。</p>
<h3 id="JS函数功能执行入口"><a href="#JS函数功能执行入口" class="headerlink" title="JS函数功能执行入口"></a>JS函数功能执行入口</h3><p>此函数是JS字节码解释的核心函数，极长，有<strong>2543行</strong>之巨，处理了方方面面的情况，整体是一个超级巨大的switch，根据字节码的opcode进行解释执行。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> JSValue <span class="title">JS_CallInternal</span><span class="params">(JSContext *caller_ctx, JSValueConst func_obj,</span></span></span><br><span class="line"><span class="params"><span class="function">                               JSValueConst this_obj, JSValueConst new_target,</span></span></span><br><span class="line"><span class="params"><span class="function">                               <span class="keyword">int</span> argc, JSValue *argv, <span class="keyword">int</span> flags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//必要的检测和初始化</span></span><br><span class="line">    ....</span><br><span class="line"></span><br><span class="line"><span class="comment">//定义CASE宏，执行每个字节码分支的时候，都会执行js_debugger_check以执行调试检测</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> CASE(op)        case_debugger_ ## op: js_debugger_check(ctx, pc); case_ ## op</span></span><br><span class="line">    </span><br><span class="line"> restart:</span><br><span class="line">    <span class="keyword">for</span>(;;) &#123;</span><br><span class="line">        <span class="keyword">int</span> call_argc;</span><br><span class="line">        JSValue *call_argv;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//调试检测函数</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> AIOTJS_ENABLE_SIM_DEBUG</span></span><br><span class="line">        js_debugger_check(ctx, <span class="literal">NULL</span>);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">        <span class="comment">//根据opcode进行dispatch处理</span></span><br><span class="line">        SWITCH(pc) &#123;</span><br><span class="line">        CASE(OP_push_i32):</span><br><span class="line">            *sp++ = JS_NewInt32(ctx, get_u32(pc));</span><br><span class="line">            pc += <span class="number">4</span>;</span><br><span class="line">            BREAK;</span><br><span class="line">        CASE(OP_push_const):</span><br><span class="line">            *sp++ = JS_DupValue(ctx, b-&gt;cpool[get_u32(pc)]);</span><br><span class="line">            pc += <span class="number">4</span>;</span><br><span class="line">            BREAK;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> SHORT_OPCODES</span></span><br><span class="line">        CASE(OP_push_minus1):</span><br><span class="line">        CASE(OP_push_0):</span><br><span class="line">        CASE(OP_push_1):</span><br><span class="line">        CASE(OP_push_2):</span><br><span class="line">        CASE(OP_push_3):</span><br><span class="line">        CASE(OP_push_4):</span><br><span class="line">        CASE(OP_push_5):</span><br><span class="line">        CASE(OP_push_6):</span><br><span class="line">        CASE(OP_push_7):</span><br><span class="line">            *sp++ = JS_NewInt32(ctx, opcode - OP_push_0);</span><br><span class="line">            BREAK;</span><br><span class="line">        CASE(OP_push_i8):</span><br><span class="line">            *sp++ = JS_NewInt32(ctx, get_i8(pc));</span><br><span class="line">            pc += <span class="number">1</span>;</span><br><span class="line">            BREAK;</span><br><span class="line">        CASE(OP_push_i16):</span><br><span class="line">            *sp++ = JS_NewInt32(ctx, get_i16(pc));</span><br><span class="line">            pc += <span class="number">2</span>;</span><br><span class="line">            BREAK;</span><br><span class="line">        CASE(OP_push_const8):</span><br><span class="line">            *sp++ = JS_DupValue(ctx, b-&gt;cpool[*pc++]);</span><br><span class="line">            BREAK;</span><br><span class="line">        CASE(OP_fclosure8):</span><br><span class="line">            *sp++ = js_closure(ctx, JS_DupValue(ctx, b-&gt;cpool[*pc++]), var_refs, sf);</span><br><span class="line">            <span class="keyword">if</span> (unlikely(JS_IsException(sp[<span class="number">-1</span>])))</span><br><span class="line">                <span class="keyword">goto</span> exception;</span><br><span class="line">            BREAK;</span><br><span class="line">        CASE(OP_push_empty_string):</span><br><span class="line">            *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string);</span><br><span class="line">            BREAK;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//异常处理</span></span><br><span class="line"> exception:</span><br><span class="line">    ....</span><br><span class="line"></span><br><span class="line">    <span class="comment">//清理及返回</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> ret_val;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="调试功能入口"><a href="#调试功能入口" class="headerlink" title="调试功能入口"></a>调试功能入口</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">js_debugger_check</span><span class="params">(JSContext* ctx, <span class="keyword">const</span> <span class="keyword">uint8_t</span> *cur_pc)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//调用js_debugger_check之前需要设置通知函数</span></span><br><span class="line">    JSDebuggerInfo *info = js_debugger_info(JS_GetRuntime(ctx));</span><br><span class="line">    <span class="keyword">if</span> (info-&gt;is_debugging)</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    <span class="keyword">if</span> (info-&gt;debugging_ctx == ctx)</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    info-&gt;is_debugging = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">// info-&gt;ctx = ctx;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//根据两个特定的环境变量来连接调试器</span></span><br><span class="line">    <span class="keyword">if</span> (!info-&gt;attempted_connect) &#123;</span><br><span class="line">        info-&gt;attempted_connect = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">char</span> *address = getenv(<span class="string">&quot;QUICKJS_DEBUG_ADDRESS&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (address != <span class="literal">NULL</span> &amp;&amp; !info-&gt;transport_close)</span><br><span class="line">            <span class="comment">//前端先启动，后端直接连接，无阻塞，无法连接则报错</span></span><br><span class="line">            js_debugger_connect(ctx, address, g_debugger_attach_notify);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (!info-&gt;attempted_wait) &#123;</span><br><span class="line">        info-&gt;attempted_wait = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">char</span> *address = getenv(<span class="string">&quot;QUICKJS_DEBUG_LISTEN_ADDRESS&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (address != <span class="literal">NULL</span> &amp;&amp; !info-&gt;transport_close)</span><br><span class="line">            <span class="comment">//后端阻塞直到连接到前端</span></span><br><span class="line">            js_debugger_wait_connection(ctx, address, g_debugger_attach_notify);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (info-&gt;transport_close == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">goto</span> done;</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">JSDebuggerLocation</span> <span class="title">location</span>;</span></span><br><span class="line">    <span class="keyword">int</span> depth;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//已暂停，单步执行中，检测location和depth</span></span><br><span class="line">    <span class="keyword">if</span> (info-&gt;stepping) &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">if</span> (已到达step位置)</span><br><span class="line">            <span class="keyword">goto</span> done;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//检测是否命中断点</span></span><br><span class="line">    <span class="keyword">int</span> at_breakpoint = js_debugger_check_breakpoint(ctx, info-&gt;breakpoints_dirty_counter, cur_pc);</span><br><span class="line">    <span class="keyword">if</span> (at_breakpoint) &#123;</span><br><span class="line">        <span class="comment">// 命中已存在的断点，向vscode发送暂停消息，暂停原因为 breakpoint</span></span><br><span class="line">        info-&gt;stepping = <span class="number">0</span>;</span><br><span class="line">        info-&gt;is_paused = <span class="number">1</span>;</span><br><span class="line">        js_send_stopped_event(info, <span class="string">&quot;breakpoint&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (info-&gt;stepping) &#123;</span><br><span class="line">        <span class="comment">//单步执行 continue</span></span><br><span class="line">        <span class="keyword">if</span> (info-&gt;stepping == JS_DEBUGGER_STEP_CONTINUE) &#123;</span><br><span class="line">            info-&gt;stepping = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (info-&gt;stepping == JS_DEBUGGER_STEP_IN) &#123;</span><br><span class="line">            <span class="comment">//单步执行 step in</span></span><br><span class="line">            <span class="keyword">int</span> depth = js_debugger_stack_depth(ctx);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (info-&gt;step_depth == depth) &#123;</span><br><span class="line">                <span class="class"><span class="keyword">struct</span> <span class="title">JSDebuggerLocation</span> <span class="title">location</span> =</span> js_debugger_current_location(ctx, cur_pc);</span><br><span class="line">                <span class="keyword">if</span> (location.filename == info-&gt;step_over.filename</span><br><span class="line">                    &amp;&amp; location.line == info-&gt;step_over.line</span><br><span class="line">                    &amp;&amp; location.column == info-&gt;step_over.column)</span><br><span class="line">                    <span class="keyword">goto</span> done;</span><br><span class="line">            &#125;</span><br><span class="line">            info-&gt;stepping = <span class="number">0</span>;</span><br><span class="line">            info-&gt;is_paused = <span class="number">1</span>;</span><br><span class="line">            js_send_stopped_event(info, <span class="string">&quot;stepIn&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (info-&gt;stepping == JS_DEBUGGER_STEP_OUT) &#123;</span><br><span class="line">            <span class="comment">//单步执行 step out</span></span><br><span class="line">            <span class="keyword">int</span> depth = js_debugger_stack_depth(ctx);</span><br><span class="line">            <span class="keyword">if</span> (depth &gt;= info-&gt;step_depth)</span><br><span class="line">                <span class="keyword">goto</span> done;</span><br><span class="line">            info-&gt;stepping = <span class="number">0</span>;</span><br><span class="line">            info-&gt;is_paused = <span class="number">1</span>;</span><br><span class="line">            js_send_stopped_event(info, <span class="string">&quot;stepOut&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (info-&gt;stepping == JS_DEBUGGER_STEP) &#123;</span><br><span class="line">            <span class="comment">//单步执行 step</span></span><br><span class="line">            struct JSDebuggerLocation location = js_debugger_current_location(ctx, cur_pc);</span><br><span class="line">            <span class="comment">// to step over, need to make sure the location changes,</span></span><br><span class="line">            <span class="comment">// and that the location change isn&#x27;t into a function call (deeper stack).</span></span><br><span class="line">            <span class="keyword">if</span> (已到达step位置)</span><br><span class="line">                <span class="keyword">goto</span> done;</span><br><span class="line">            info-&gt;stepping = <span class="number">0</span>;</span><br><span class="line">            info-&gt;is_paused = <span class="number">1</span>;</span><br><span class="line">            js_send_stopped_event(info, <span class="string">&quot;step&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// ???</span></span><br><span class="line">            info-&gt;stepping = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//读取并处理调试消息，主要是从前端发送的命令请求，入断点，查询变量值等</span></span><br><span class="line">    <span class="comment">//内部调用了js_process_debugger_messages</span></span><br><span class="line">    <span class="keyword">int</span> ret = js_debugger_process_server_message(ctx);</span><br><span class="line">    <span class="keyword">if</span>(ret == <span class="number">-1</span>)</span><br><span class="line">        <span class="keyword">goto</span> fail;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span>(ret == <span class="number">1</span>)</span><br><span class="line">        <span class="keyword">goto</span> done;</span><br><span class="line">    <span class="comment">//前端调试命令处理函数</span></span><br><span class="line">    <span class="keyword">if</span> (js_process_debugger_messages(info, cur_pc))</span><br><span class="line">        <span class="keyword">goto</span> done;</span><br><span class="line"></span><br><span class="line">    fail: </span><br><span class="line">        js_debugger_free(JS_GetRuntime(ctx), info);</span><br><span class="line">    done:</span><br><span class="line">        info-&gt;is_debugging = <span class="number">0</span>;</span><br><span class="line">        <span class="comment">// info-&gt;ctx = NULL;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="调试命令处理"><a href="#调试命令处理" class="headerlink" title="调试命令处理"></a>调试命令处理</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">js_process_debugger_messages</span><span class="params">(JSDebuggerInfo *info, <span class="keyword">const</span> <span class="keyword">uint8_t</span> *cur_pc)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// continue processing messages until the continue message is received.</span></span><br><span class="line">    JSContext *ctx = info-&gt;ctx;</span><br><span class="line">    <span class="keyword">if</span>(ctx == <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">DebuggerSuspendedState</span> <span class="title">state</span>;</span></span><br><span class="line">    state.variable_reference_count = js_debugger_stack_depth(ctx) &lt;&lt; <span class="number">2</span>;</span><br><span class="line">    state.variable_pointers = JS_NewObject(ctx);</span><br><span class="line">    state.variable_references = JS_NewObject(ctx);</span><br><span class="line">    state.cur_pc = cur_pc;</span><br><span class="line">    <span class="keyword">int</span> ret = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">char</span> message_length_buf[<span class="number">10</span>];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">        <span class="comment">//检测和分配buffer缓冲区</span></span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">        <span class="comment">//从socket中读取前端发来的json命令</span></span><br><span class="line">        <span class="keyword">if</span> (!js_transport_read_fully(info, info-&gt;message_buffer, message_length))</span><br><span class="line">            <span class="keyword">goto</span> done;</span><br><span class="line">        </span><br><span class="line">        info-&gt;message_buffer[message_length] = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//JSON.parse转换成js json对象</span></span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">        <span class="comment">//解析请求类型</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">strcmp</span>(<span class="string">&quot;request&quot;</span>, type) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">//request请求</span></span><br><span class="line">            js_process_request(info, &amp;state, JS_GetPropertyStr(ctx, message, <span class="string">&quot;request&quot;</span>));</span><br><span class="line">            <span class="comment">// done_processing = 1;</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(<span class="string">&quot;continue&quot;</span>, type) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">//继续执行</span></span><br><span class="line">            info-&gt;is_paused = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(<span class="string">&quot;breakpoints&quot;</span>, type) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">//处理断点消息</span></span><br><span class="line">            js_process_breakpoints(info, JS_GetPropertyStr(ctx, message, <span class="string">&quot;breakpoints&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">strcmp</span>(<span class="string">&quot;stopOnException&quot;</span>, type) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">//处理异常暂停</span></span><br><span class="line">            JSValue stop = JS_GetPropertyStr(ctx, message, <span class="string">&quot;stopOnException&quot;</span>);</span><br><span class="line">            info-&gt;exception_breakpoint = JS_ToBool(ctx, stop);</span><br><span class="line">            JS_FreeValue(ctx, stop);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//清理临时js对象</span></span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (info-&gt;is_paused);</span><br><span class="line"></span><br><span class="line">    ret = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">done:</span><br><span class="line">    <span class="comment">//清理临时js对象</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="quickjs调试功能小结"><a href="#quickjs调试功能小结" class="headerlink" title="quickjs调试功能小结"></a>quickjs调试功能小结</h3><p>此处我们不再继续向里展开，再聊下去无非是实现层面的一些细节，考虑到我们这里大部分都是前端开发者，对C代码的实现细节我们不再做进一步的阐述。<br>本文主旨在阐述调试器实现原理和给出一个内容梗概，相较于扣细节，我更喜欢讲原理，因为细节必定依赖于特定语言而原理是一些更通用的解题思路，思路打开了，很多东西也就迎刃而解了。<br>下面我们对调试流程做个补充和总结：</p>
<ul>
<li>quickjs调试器使用server/client模式进行开发，VSCode为server，quickjs引擎为client。</li>
<li>server/client之间使用socket进行连接，通过DAP协议（JSON）进行通信。</li>
<li>每解释执行一条js字节码，都会调用<strong>js_debugger_check</strong>，尝试连接调试器，接收并处理调试消息。</li>
<li>DAP协议定义和规范了调试消息的格式，在VSCode调试界面上的操作（如下断点）最终会生成一条对应的JSON消息，通过socket发送给js引擎</li>
<li>引擎解析从server收到的json消息，根据请求类型，完成如下断点/获取变量值/单步执行等操作。</li>
<li>引擎执行完指定操作之后，会向server发送reply消息，通知sever操作已完成或报告错误，VSCode会根据返回的消息更新调试界面（单步执行后移动到下一行/将引擎中最新的变量值更新到变量窗口）</li>
</ul>
<h2 id="深入探究：quickjs断点实现原理详解"><a href="#深入探究：quickjs断点实现原理详解" class="headerlink" title="深入探究：quickjs断点实现原理详解"></a>深入探究：quickjs断点实现原理详解</h2><p>在上面的原理解析部分，我们简单聊过了如何将字节码和源码做对应，现在我们要落实到实处，看一下在quickjs中，是如何存储这个映射关系，或者叫<strong>调试信息</strong>的，又是如何正确命中断点的。<br>想要弄懂这些，需要深入到quickjs字节码生成和实现的一些细节，因为调试信息也是quickjs字节码的一部分，我们将聚焦于调试信息相关的部分带领大家做一个简单了解。这部分不可避免地要了解quickjs的C代码实现的部分细节，我们会尽量简略，省略掉不必要的细节，仅关注和断点相关的部分。<br>来，让我们一睹调试器的庐山真面目。</p>
<h3 id="js函数定义结构体"><a href="#js函数定义结构体" class="headerlink" title="js函数定义结构体"></a>js函数定义结构体</h3><p>quickjs中，js代码的编译解析是以function为最小单元的，引擎会解析js源文件，以js函数为单位生成字节码，我们先来看一个js函数在引擎中的定义：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">JSFunctionDef</span> &#123;</span></span><br><span class="line">    JSContext *ctx;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    DynBuf byte_code;       <span class="comment">//用于保存函数对应字节码的数组</span></span><br><span class="line">    <span class="keyword">int</span> last_opcode_pos; <span class="comment">/* -1 if no last opcode */</span></span><br><span class="line">    <span class="keyword">int</span> last_opcode_line_num;</span><br><span class="line">    BOOL use_short_opcodes; <span class="comment">/* true if short opcodes are used in byte_code */</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">//保存标签符号</span></span><br><span class="line">    LabelSlot *label_slots;</span><br><span class="line">    <span class="keyword">int</span> label_size; <span class="comment">/* allocated size for label_slots[] */</span></span><br><span class="line">    <span class="keyword">int</span> label_count;</span><br><span class="line">    BlockEnv *top_break; <span class="comment">/* break/continue label stack */</span></span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//保存行号</span></span><br><span class="line">    LineNumberSlot *line_number_slots;</span><br><span class="line">    <span class="keyword">int</span> line_number_size;</span><br><span class="line">    <span class="keyword">int</span> line_number_count;</span><br><span class="line">    <span class="keyword">int</span> line_number_last;</span><br><span class="line">    <span class="keyword">int</span> line_number_last_pc;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* pc2line table */</span></span><br><span class="line">    JSAtom filename;</span><br><span class="line">    <span class="keyword">int</span> line_num;</span><br><span class="line">    DynBuf pc2line;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//对应源码片段</span></span><br><span class="line">    <span class="keyword">char</span> *source;  <span class="comment">/* raw source, utf-8 encoded */</span></span><br><span class="line">    <span class="keyword">int</span> source_len;</span><br><span class="line"></span><br><span class="line">    JSModuleDef *<span class="keyword">module</span>; <span class="comment">/* != NULL when parsing a module */</span></span><br><span class="line">&#125; JSFunctionDef;</span><br></pre></td></tr></table></figure>
<p>上面的定义省略了和调试无关的部分，可以看到函数结构体中保存了函数中的标签符号信息，行号信息，pc2line(字节码到行号)映射信息，源码片段等。</p>
<h3 id="eval时解析js源码"><a href="#eval时解析js源码" class="headerlink" title="eval时解析js源码"></a>eval时解析js源码</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,</span><br><span class="line">                                 <span class="keyword">const</span> <span class="keyword">char</span> *input, <span class="keyword">size_t</span> input_len,</span><br><span class="line">                                 <span class="keyword">const</span> <span class="keyword">char</span> *filename, <span class="keyword">int</span> flags, <span class="keyword">int</span> scope_idx)</span><br><span class="line">&#123;</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//初始化parser，跳过注释</span></span><br><span class="line">    js_parse_init(ctx, s, input, input_len, filename);</span><br><span class="line">    skip_shebang(s);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//处理各种flag</span></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//创建函数定义结构，即 JSFunctionDef的实例</span></span><br><span class="line">    fd = js_new_function_def(ctx, <span class="literal">NULL</span>, TRUE, FALSE, filename, <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (!fd)</span><br><span class="line">        <span class="keyword">goto</span> fail1;</span><br><span class="line">    <span class="comment">//对fd进行赋值</span></span><br><span class="line">    ...</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//使用JSParseState结构解析源码</span></span><br><span class="line">    err = js_parse_program(s);</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    fail:</span><br><span class="line">        free_token(s, &amp;s-&gt;token);</span><br><span class="line">        js_free_function_def(ctx, fd);</span><br><span class="line">        <span class="keyword">goto</span> fail1;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//创建函数</span></span><br><span class="line">    <span class="comment">/* create the function object and all the enclosed functions */</span></span><br><span class="line">    fun_obj = js_create_function(ctx, fd);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//错误检测和处理</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> JS_EXCEPTION;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="生成字节码时保存行号信息"><a href="#生成字节码时保存行号信息" class="headerlink" title="生成字节码时保存行号信息"></a>生成字节码时保存行号信息</h3><p><strong>js_parse_program</strong>是字节码生成的主体函数，通过一个JSParseState结构体对js进行语法解析并生成字节码，</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> __exception <span class="keyword">int</span> <span class="title">js_parse_program</span><span class="params">(JSParseState *s)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    JSFunctionDef *fd = s-&gt;cur_func;</span><br><span class="line">    <span class="keyword">int</span> idx;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (next_token(s))</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (js_parse_directives(s))</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">    fd-&gt;is_global_var = (fd-&gt;eval_type == JS_EVAL_TYPE_GLOBAL) ||</span><br><span class="line">        (fd-&gt;eval_type == JS_EVAL_TYPE_MODULE) ||</span><br><span class="line">        !(fd-&gt;js_mode &amp; JS_MODE_STRICT);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!s-&gt;is_module) &#123;</span><br><span class="line">        <span class="comment">/* hidden variable for the return value */</span></span><br><span class="line">        fd-&gt;eval_ret_idx = idx = add_var(s-&gt;ctx, fd, JS_ATOM__ret_);</span><br><span class="line">        <span class="keyword">if</span> (idx &lt; <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (s-&gt;token.val != TOK_EOF) &#123;</span><br><span class="line">        <span class="comment">//顺序解析直到结尾</span></span><br><span class="line">        <span class="keyword">if</span> (js_parse_source_element(s))</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!s-&gt;is_module) &#123;</span><br><span class="line">        <span class="comment">/* return the value of the hidden variable eval_ret_idx  */</span></span><br><span class="line">        emit_op(s, OP_get_loc);</span><br><span class="line">        emit_u16(s, fd-&gt;eval_ret_idx);</span><br><span class="line"></span><br><span class="line">        emit_op(s, OP_return);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        emit_op(s, OP_return_undef);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>解析的过程嵌套较深，会针对不同的语法元素调用不同的子过程函数进行解析，我们来看其中的一个函数以理解quickjs如何在生成字节码的同时保存行号信息。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">emit_op</span><span class="params">(JSParseState *s, <span class="keyword">uint8_t</span> val)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    JSFunctionDef *fd = s-&gt;cur_func;</span><br><span class="line">    DynBuf *bc = &amp;fd-&gt;byte_code;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Use the line number of the last token used, not the next token,</span></span><br><span class="line"><span class="comment">       nor the current offset in the source file.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">if</span> (unlikely(fd-&gt;last_opcode_line_num != s-&gt;last_line_num)) &#123;</span><br><span class="line">        <span class="comment">//在字节码数组中保存行号信息，对应的opcode是 OP_line_num</span></span><br><span class="line">        dbuf_putc(bc, OP_line_num);</span><br><span class="line">        dbuf_put_u32(bc, s-&gt;last_line_num);</span><br><span class="line">        fd-&gt;last_opcode_line_num = s-&gt;last_line_num;</span><br><span class="line">    &#125;</span><br><span class="line">    fd-&gt;last_opcode_pos = bc-&gt;size;</span><br><span class="line">    dbuf_putc(bc, val);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面给出一个quickjs的语法分析器的工作流程，对quickjs源码感兴趣的同学可以参考系列博文<a target="_blank" rel="noopener" href="https://www.zhihu.com/column/c_1337176691518140416">QuickJS剖析</a><br><img src="/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-23-10-26-16.png"></p>
<h3 id="创建函数并保存pc2line信息"><a href="#创建函数并保存pc2line信息" class="headerlink" title="创建函数并保存pc2line信息"></a>创建函数并保存pc2line信息</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> JSValue <span class="title">js_create_function</span><span class="params">(JSContext *ctx, JSFunctionDef *fd)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//处理scop，variable，eval，module等细节</span></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//遍历当前函数中定义的子函数，优先创建child，最后创建本体</span></span><br><span class="line">    <span class="comment">/* first create all the child functions */</span></span><br><span class="line">    list_for_each_safe(el, el1, &amp;fd-&gt;child_list) &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="comment">//创建子函数</span></span><br><span class="line">        func_obj = js_create_function(ctx, fd1);</span><br><span class="line">        <span class="comment">//错误检测</span></span><br><span class="line">        ...</span><br><span class="line">        <span class="comment">//将子函数对象存入cpool数组</span></span><br><span class="line">        fd-&gt;cpool[cpool_idx] = func_obj;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//解析变量</span></span><br><span class="line">    <span class="keyword">if</span> (resolve_variables(ctx, fd))</span><br><span class="line">        <span class="keyword">goto</span> fail;</span><br><span class="line">    <span class="comment">//解析label符号，内部会收集并计算pc2line信息</span></span><br><span class="line">    <span class="keyword">if</span> (resolve_labels(ctx, fd))</span><br><span class="line">        <span class="keyword">goto</span> fail;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    <span class="comment">//分配JSFunctionBytecode结构体</span></span><br><span class="line">    b = js_mallocz(ctx, function_size);</span><br><span class="line">    <span class="keyword">if</span> (!b)</span><br><span class="line">        <span class="keyword">goto</span> fail;</span><br><span class="line">    b-&gt;header.ref_count = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//将parse时生成的字节码拷贝到JSFunctionBytecode结构中保存</span></span><br><span class="line">    b-&gt;byte_code_buf = (<span class="keyword">void</span> *)((<span class="keyword">uint8_t</span>*)b + byte_code_offset);</span><br><span class="line">    b-&gt;byte_code_len = fd-&gt;byte_code.size;</span><br><span class="line">    <span class="built_in">memcpy</span>(b-&gt;byte_code_buf, fd-&gt;byte_code.buf, fd-&gt;byte_code.size);</span><br><span class="line">    <span class="comment">//释放fd-&gt;byte_code</span></span><br><span class="line">    js_free(ctx, fd-&gt;byte_code.buf);</span><br><span class="line">    fd-&gt;byte_code.buf = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">//保存函数名</span></span><br><span class="line">    b-&gt;func_name = fd-&gt;func_name;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (fd-&gt;js_mode &amp; JS_MODE_STRIP) &#123;</span><br><span class="line">        <span class="comment">//strip模式，去掉调试信息，释放pc2line结构体</span></span><br><span class="line">        JS_FreeAtom(ctx, fd-&gt;filename);</span><br><span class="line">        dbuf_free(&amp;fd-&gt;pc2line);    <span class="comment">// probably useless</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//非strip模式（debug模式），提取pc2line结构体</span></span><br><span class="line">        b-&gt;has_debug = <span class="number">1</span>;</span><br><span class="line">        <span class="comment">//保存函数所在文件名</span></span><br><span class="line">        b-&gt;debug.filename = fd-&gt;filename;</span><br><span class="line">        <span class="comment">//保存函数所在行</span></span><br><span class="line">        b-&gt;debug.line_num = fd-&gt;line_num;</span><br><span class="line">        <span class="comment">//分配pc2line缓冲区</span></span><br><span class="line">        b-&gt;debug.pc2line_buf = js_realloc(ctx, fd-&gt;pc2line.buf, fd-&gt;pc2line.size);</span><br><span class="line">        <span class="comment">//将fd的pc2line.buf保存到 b-&gt;debug.pc2line_buf</span></span><br><span class="line">        <span class="keyword">if</span> (!b-&gt;debug.pc2line_buf)</span><br><span class="line">            b-&gt;debug.pc2line_buf = fd-&gt;pc2line.buf;</span><br><span class="line">        b-&gt;debug.pc2line_len = fd-&gt;pc2line.size;</span><br><span class="line">        b-&gt;debug.source = fd-&gt;source;</span><br><span class="line">        b-&gt;debug.source_len = fd-&gt;source_len;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> JS_EXCEPTION;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>resolve_labels函数需要重点关注，在解析label的同时，此函数中会调用<strong>add_pc2line_info</strong>函数添加行号信息到pc2line table</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> __exception <span class="keyword">int</span> <span class="title">resolve_labels</span><span class="params">(JSContext *ctx, JSFunctionDef *s)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//遍历JSFunctionDef中保存的字节码数组，解析每条指令</span></span><br><span class="line">    <span class="keyword">for</span> (pos = <span class="number">0</span>; pos &lt; bc_len; pos = pos_next) &#123;</span><br><span class="line">        <span class="keyword">int</span> val;</span><br><span class="line">        op = bc_buf[pos];</span><br><span class="line">        len = opcode_info[op].size;</span><br><span class="line">        pos_next = pos + len;</span><br><span class="line">        <span class="keyword">switch</span>(op) &#123;</span><br><span class="line">        <span class="keyword">case</span> OP_line_num:</span><br><span class="line">            <span class="comment">//OP_line_num指令，读取字节码中保存的行号信息</span></span><br><span class="line">            line_num = get_u32(bc_buf + pos + <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">case</span> OP_label:</span><br><span class="line">            &#123;</span><br><span class="line">                ...</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">case</span> OP_call:</span><br><span class="line">        <span class="keyword">case</span> OP_call_method:</span><br><span class="line">            &#123;</span><br><span class="line">                ...</span><br><span class="line">                <span class="comment">//添加pc2line_info，line_num的来源是之前字节码中保存的行号</span></span><br><span class="line">                <span class="comment">//pc的来源是遍历时的字节码数组下标</span></span><br><span class="line">                add_pc2line_info(s, bc_out.size, line_num);</span><br><span class="line">                put_short_code(&amp;bc_out, op, argc);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">goto</span> no_change;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">case</span> OP_return:</span><br><span class="line">        <span class="keyword">case</span> OP_return_undef:</span><br><span class="line">        <span class="keyword">case</span> OP_return_async:</span><br><span class="line">        <span class="keyword">case</span> OP_throw:</span><br><span class="line">        <span class="keyword">case</span> OP_throw_error:</span><br><span class="line">            pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, &amp;line_num);</span><br><span class="line">            <span class="keyword">goto</span> no_change;</span><br><span class="line"></span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    js_free(ctx, s-&gt;label_slots);</span><br><span class="line">    s-&gt;label_slots = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">//所有的pc2line信息已添加，编码计算最终的pc2line数组</span></span><br><span class="line">    compute_pc2line_info(s);</span><br><span class="line">    js_free(ctx, s-&gt;line_number_slots);</span><br><span class="line">    s-&gt;line_number_slots = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">/* set the new byte code */</span></span><br><span class="line">    dbuf_free(&amp;s-&gt;byte_code);</span><br><span class="line">    s-&gt;byte_code = bc_out;</span><br><span class="line">    s-&gt;use_short_opcodes = TRUE;</span><br><span class="line">    <span class="keyword">if</span> (dbuf_error(&amp;s-&gt;byte_code)) &#123;</span><br><span class="line">        JS_ThrowOutOfMemory(ctx);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> fail:</span><br><span class="line">    <span class="comment">/* <span class="doctag">XXX:</span> not safe */</span></span><br><span class="line">    dbuf_free(&amp;bc_out);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>


<p>add_pc2line_info将pc2line信息添加到JSFunctionDef结构体的<strong>line_number_slots</strong>数组中</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">add_pc2line_info</span><span class="params">(JSFunctionDef *s, <span class="keyword">uint32_t</span> pc, <span class="keyword">int</span> line_num)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (s-&gt;line_number_slots != <span class="literal">NULL</span></span><br><span class="line">    &amp;&amp;  s-&gt;line_number_count &lt; s-&gt;line_number_size</span><br><span class="line">    &amp;&amp;  pc &gt;= s-&gt;line_number_last_pc</span><br><span class="line">    &amp;&amp;  line_num != s-&gt;line_number_last) &#123;</span><br><span class="line">        <span class="comment">//记录当前的pc值和行号</span></span><br><span class="line">        s-&gt;line_number_slots[s-&gt;line_number_count].pc = pc;</span><br><span class="line">        s-&gt;line_number_slots[s-&gt;line_number_count].line_num = line_num;</span><br><span class="line">        s-&gt;line_number_count++;</span><br><span class="line">        s-&gt;line_number_last_pc = pc;</span><br><span class="line">        s-&gt;line_number_last = line_num;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>resolve_labels函数的最后，当所有的行号信息都添加完毕后，调用<strong>compute_pc2line_info</strong>来编码最终的pc2line信息，目的是为了节约空间</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">compute_pc2line_info</span><span class="params">(JSFunctionDef *s)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!(s-&gt;js_mode &amp; JS_MODE_STRIP) &amp;&amp; s-&gt;line_number_slots) &#123;</span><br><span class="line">        <span class="keyword">int</span> last_line_num = s-&gt;line_num;</span><br><span class="line">        <span class="keyword">uint32_t</span> last_pc = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">int</span> i;</span><br><span class="line"></span><br><span class="line">        js_dbuf_init(s-&gt;ctx, &amp;s-&gt;pc2line);</span><br><span class="line">        <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; s-&gt;line_number_count; i++) &#123;</span><br><span class="line">            <span class="keyword">uint32_t</span> pc = s-&gt;line_number_slots[i].pc;</span><br><span class="line">            <span class="keyword">int</span> line_num = s-&gt;line_number_slots[i].line_num;</span><br><span class="line">            <span class="keyword">int</span> diff_pc, diff_line;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (line_num &lt; <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">//计算pc和line的差值</span></span><br><span class="line">            diff_pc = pc - last_pc;</span><br><span class="line">            diff_line = line_num - last_line_num;</span><br><span class="line">            <span class="keyword">if</span> (diff_line == <span class="number">0</span> || diff_pc &lt; <span class="number">0</span>)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">//将diff_pc和diff_line编码为一个值，方式是 通过除PC2LINE_RANGE取余,举个例子</span></span><br><span class="line">            <span class="comment">//z = x * 5 + y</span></span><br><span class="line">            <span class="comment">//x = z / 5</span></span><br><span class="line">            <span class="comment">//y = z % 5</span></span><br><span class="line">            <span class="comment">//可以编码，编码以节约空间</span></span><br><span class="line">            <span class="keyword">if</span> (diff_line &gt;= PC2LINE_BASE &amp;&amp;</span><br><span class="line">                diff_line &lt; PC2LINE_BASE + PC2LINE_RANGE &amp;&amp;</span><br><span class="line">                diff_pc &lt;= PC2LINE_DIFF_PC_MAX) &#123;</span><br><span class="line">                dbuf_putc(&amp;s-&gt;pc2line, (diff_line - PC2LINE_BASE) +</span><br><span class="line">                          diff_pc * PC2LINE_RANGE + PC2LINE_OP_FIRST);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">//容纳不下，将值直接存入</span></span><br><span class="line">                <span class="comment">/* longer encoding */</span></span><br><span class="line">                dbuf_putc(&amp;s-&gt;pc2line, <span class="number">0</span>);</span><br><span class="line">                dbuf_put_leb128(&amp;s-&gt;pc2line, diff_pc);</span><br><span class="line">                dbuf_put_sleb128(&amp;s-&gt;pc2line, diff_line);</span><br><span class="line">            &#125;</span><br><span class="line">            last_pc = pc;</span><br><span class="line">            last_line_num = line_num;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="调试信息生成流程"><a href="#调试信息生成流程" class="headerlink" title="调试信息生成流程"></a>调试信息生成流程</h3><p><img src="/2021/09/17/quickjs%E8%B0%83%E8%AF%95%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E8%A7%A3%E6%9E%90/IMG_2021-09-18-19-09-56.png" alt="调试信息生成流程"></p>
<h3 id="使用pc2line信息检查是否命中断点"><a href="#使用pc2line信息检查是否命中断点" class="headerlink" title="使用pc2line信息检查是否命中断点"></a>使用pc2line信息检查是否命中断点</h3><p>引擎解释执行一条js字节码，都会调用<strong>js_debugger_check</strong>，其内部会调用<strong>js_debugger_check_breakpoint</strong>已检查是否命中断点，我们来看这个函数的实现细节：</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">js_debugger_check_breakpoint</span><span class="params">(JSContext *ctx, <span class="keyword">uint32_t</span> current_dirty, <span class="keyword">const</span> <span class="keyword">uint8_t</span> *cur_pc)</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//p指向了便是我们之前通过compute_pc2line_info计算出的pc2line编码信息缓冲区开头</span></span><br><span class="line">    p = b-&gt;debug.pc2line_buf;</span><br><span class="line">    <span class="comment">//p_end指向了pc2line编码信息缓冲区末尾</span></span><br><span class="line">    p_end = p + b-&gt;debug.pc2line_len;</span><br><span class="line">    pc = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">//line_num赋值为b-&gt;debug.line_num，这个值是函数所在行号</span></span><br><span class="line">    line_num = b-&gt;debug.line_num;</span><br><span class="line">    <span class="comment">//遍历所有的断点信息以检测它们是否命中</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">uint32_t</span> i = <span class="number">0</span>; i &lt; breakpoints_length; i++) &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">int</span> last_line_num = line_num;</span><br><span class="line">        <span class="keyword">int</span> line_pc = pc;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果没扫到pc2line末尾且扫描行号不大于断点行号，则不断扫描</span></span><br><span class="line">        <span class="keyword">while</span> (p &lt; p_end &amp;&amp; line_num &lt;= breakpoint_line) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 按行扫描，直到到达新行</span></span><br><span class="line">            <span class="keyword">while</span> (p &lt; p_end &amp;&amp; line_num == last_line_num) &#123;</span><br><span class="line">                op = *p++;</span><br><span class="line">                <span class="comment">//op是0对应着pc2line无法编码的情况，直接读取</span></span><br><span class="line">                <span class="keyword">if</span> (op == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">uint32_t</span> val;</span><br><span class="line">                    ret = get_leb128(&amp;val, p, p_end);</span><br><span class="line">                    <span class="keyword">if</span> (ret &lt; <span class="number">0</span>)</span><br><span class="line">                        <span class="keyword">goto</span> fail;</span><br><span class="line">                    pc += val;</span><br><span class="line">                    p += ret;</span><br><span class="line">                    ret = get_sleb128(&amp;v, p, p_end);</span><br><span class="line">                    <span class="keyword">if</span> (ret &lt; <span class="number">0</span>)</span><br><span class="line">                        <span class="keyword">goto</span> fail;</span><br><span class="line">                    p += ret;</span><br><span class="line">                    new_line_num = line_num + v;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">//可以编码，从op中解码出 diff_pc和diff_line</span></span><br><span class="line">                    op -= PC2LINE_OP_FIRST;</span><br><span class="line">                    pc += (op / PC2LINE_RANGE);</span><br><span class="line">                    <span class="comment">//计算新行，使用diff_line + line_num得到</span></span><br><span class="line">                    new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE;</span><br><span class="line">                &#125;</span><br><span class="line">                line_num = new_line_num;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//找到新行，则判断它是否命中断点</span></span><br><span class="line">            <span class="keyword">if</span> (line_num != last_line_num) &#123;</span><br><span class="line">                <span class="comment">// new line found, check if it is the one with breakpoint.</span></span><br><span class="line">                <span class="keyword">if</span> (last_line_num == breakpoint_line &amp;&amp; line_num &gt; last_line_num)</span><br><span class="line">                    <span class="comment">//命中，将命中行对应的指令位置设置为1，标记它们被设置了断点</span></span><br><span class="line">                    <span class="comment">//这样做的理由是，一行的js源码可能对应多条字节码指令，所以要将一段位置都设置成1，代码运行到这个区段中都视作命中断点</span></span><br><span class="line">                    <span class="built_in">memset</span>(b-&gt;debugger.breakpoints + line_pc, <span class="number">1</span>, pc - line_pc);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// update the line trackers</span></span><br><span class="line">                line_pc = pc;</span><br><span class="line">                last_line_num = line_num;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (p &gt;= p_end)</span><br><span class="line">            b-&gt;debugger.last_line_num = line_num;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">fail:</span><br><span class="line">    JS_FreeValue(ctx, breakpoints);</span><br><span class="line"></span><br><span class="line">done:</span><br><span class="line">    JS_FreeValue(ctx, path_data);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!b-&gt;debugger.breakpoints)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    pc = (cur_pc ? cur_pc : ctx-&gt;rt-&gt;current_stack_frame-&gt;cur_pc) - b-&gt;byte_code_buf - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">if</span> (pc &lt; <span class="number">0</span> || pc &gt; b-&gt;byte_code_len)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="comment">//返回以pc为下标的数组值，根据上面的设置，1则表示命中</span></span><br><span class="line">    <span class="keyword">return</span> b-&gt;debugger.breakpoints[pc];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



    </div>

    
    
    

    <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/quickjs/" rel="tag"># quickjs</a>
          </div>

        

          <div class="post-nav">
            <div class="post-nav-item">
                <a href="/2020/11/27/%E9%87%8D%E6%9E%84%E7%89%88Launcher%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5%EF%BC%9ACEF%E7%AF%87/" rel="prev" title="重构版Launcher开发实践01：CEF篇">
                  <i class="fa fa-chevron-left"></i> 重构版Launcher开发实践01：CEF篇
                </a>
            </div>
            <div class="post-nav-item">
            </div>
          </div>
    </footer>
  </article>
</div>






</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Alex Peng</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/pisces/" rel="noopener" target="_blank">NexT.Pisces</a> 强力驱动
  </div>

    </div>
  </footer>

  
  <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js" integrity="sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY=" crossorigin="anonymous"></script>
<script src="/js/comments.js"></script><script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/next-boot.js"></script>

  
<script src="/js/third-party/search/local-search.js"></script>




  





</body>
</html>
